/*
 * Toedo
 * Copyright (C) 2009-2010 J. Devauchelle and contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package org.toedo.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.toedo.controllers.Controller;
import org.toedo.controllers.events.EventConstants;
import org.toedo.controllers.events.EventController;
import org.toedo.gui.actions.OpenNoteAction;
import org.toedo.gui.actions.OpenTaskAction;
import org.toedo.gui.translations.Messages;
import org.toedo.model.Attachment.AttachmentType;
import org.toedo.model.Task.TaskStatus;
import org.toedo.model.comparators.NoteComparator;
import org.toedo.model.comparators.TagComparator;
import org.toedo.model.comparators.TaskComparator;
import org.toedo.model.enums.SortDirection;
import org.toedo.model.enums.WikiMarkupType;
import org.toedo.preferences.PreferenceConstants;
import org.toedo.utils.ApplicationUtils;
import org.toedo.utils.Constants;

import com.thoughtworks.xstream.annotations.XStreamOmitField;

public class Project extends XStreamSerializableObject {

	private String mName;
	private int mStartDateDelay;
	private int mDefaultDuration;
	private int mUrgentDelay;
	private int mDefaultPriority;
	private WikiMarkupType mMarkupType;
	
	private String mDisplayCss;
	
	private Map<UUID, Task> mTasksMap;
	private Map<UUID, Note> mNotesMap;
	private Map<UUID, Tag> mTagsMap;
	
	@XStreamOmitField
	private boolean mIsModified = false;
	
	public Project() {
		mName = Constants.PROJECT_DEFAULT_NAME;
		mStartDateDelay = Controller.getInstance().getPreferenceStore().getInt(PreferenceConstants.PROJECT_DEFAULT_START_DATE_DELAY);
		mDefaultDuration = Controller.getInstance().getPreferenceStore().getInt(PreferenceConstants.PROJECT_DEFAULT_TASK_DURATION);
		mUrgentDelay = Controller.getInstance().getPreferenceStore().getInt(PreferenceConstants.PROJECT_DEFAULT_URGENT_DELAY);
		mDefaultPriority = Controller.getInstance().getPreferenceStore().getInt(PreferenceConstants.PROJECT_DEFAULT_TASK_PRIORITY);
		mMarkupType = WikiMarkupType.getValueOf(Controller.getInstance().getPreferenceStore().getString(PreferenceConstants.PROJECT_DEFAULT_WIKI_MARKUP));
		
		mDisplayCss = ""; //$NON-NLS-1$
		
		mNotesMap = new HashMap<UUID, Note>();
		mTasksMap = new HashMap<UUID, Task>();
		mTagsMap = new HashMap<UUID, Tag>();
	}
	
	@Override
	protected Object readResolve() {
	    if (mNotesMap == null) {
	    	mNotesMap = new HashMap<UUID, Note>();
	    }
	    
	    if (mTasksMap == null) {
	    	mTasksMap = new HashMap<UUID, Task>();
	    }
	    
	    if (mTagsMap == null) {
	    	mTagsMap = new HashMap<UUID, Tag>();
	    }
	    
	    if (mMarkupType == null) {
	    	mMarkupType = WikiMarkupType.EnTextile;
	    }
	    if (mDisplayCss == null) {
	    	mDisplayCss = ""; //$NON-NLS-1$
	    }
	    return this;
	  }

	
	public String getProjectName() {
		return mName;
	}
	
	public void setProjectName(String value) {
		mName = value;
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_NAME_CHANGE);
	}
	
	public int getStartDateDelay() {
		return mStartDateDelay;
	}

	public void setStartDateDelay(int mStartDateDelay) {
		this.mStartDateDelay = mStartDateDelay;
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_START_DELAY_CHANGE);
	}

	public int getDefaultDuration() {
		return mDefaultDuration;
	}

	public void setDefaultDuration(int mDueDateDelay) {
		this.mDefaultDuration = mDueDateDelay;
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_DEFAULT_DURATION_CHANGE);
	}

	public int getUrgentDelay() {
		return mUrgentDelay;
	}

	public void setUrgentDelay(int mUrgentDelay) {
		this.mUrgentDelay = mUrgentDelay;
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_URGENT_DELAY_CHANGE);
	}

	public int getDefaultPriority() {
		return mDefaultPriority;
	}

	public void setDefaultPriority(int mDefaultPriority) {
		this.mDefaultPriority = mDefaultPriority;
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_DEFAULT_PRIORITY_CHANGE);
	}
	
	public WikiMarkupType getMarkupType() {
		return mMarkupType;
	}
	
	public void setMarkupType(WikiMarkupType value) {
		mMarkupType = value;
		mIsModified = true;
		
		setBaseNotesNeedContentRefresh();
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_MARKUP_CHANGE);
	}
	
	public String getDisplayCss() {
		return mDisplayCss;
	}
	
	public void setDisplayCss(String value) {
		mDisplayCss = value;
		mIsModified = true;
		
		setBaseNotesNeedContentRefresh();
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_PROJECT_PROPERTIES_CHANGE, null, EventConstants.EVT_PROJECT_PROPERTIES_DISPLAY_CSS_CHANGE);
	}

	private void setBaseNotesNeedContentRefresh() {
		for (Task task : mTasksMap.values()) {
			task.setNeedContentRefresh();
		}
		
		for (Note note : mNotesMap.values()) {
			note.setNeedContentRefresh();
		}
	}
	
	private void internalAddTask(Task task) {
		mTasksMap.put(task.getId(), task);		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_ADD_TASK, null, null);
	}
	
	public Task addTask(String title) {
		Task task = new Task(title);
		
		internalAddTask(task);
		
		return task;
	}
	
	public Task addTask(String title, String content, Date startDate, Date dueDate, int priority) {
		Task task = new Task(title, content, startDate, dueDate, priority);
		
		internalAddTask(task);
		
		return task;
	}
	
	public void removeTask(Task task) {
		mTasksMap.remove(task.getId());
		
		// First copy to a temporary list, as modifying a list we're looping on does not work. 
		List<Tag> tagList = new ArrayList<Tag>();
		for (Tag tag : task.getTagsList()) {
			tagList.add(tag);
		}
		
		for (Tag tag : tagList) {
			tag.removeTask(task);
		}
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_DELETE_TASK, null, task);
	}
	
	public Note addNote(String title) {
		Note note = new Note(title);
		mNotesMap.put(note.getId(), note);
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_ADD_NOTE, null, null);
		
		return note;
	}
	
	public void removeNote(Note note) {
		mNotesMap.remove(note.getId());
		
		// First copy to a temporary list, as modifying a list we're looping on does not work. 
		List<Tag> tagList = new ArrayList<Tag>();
		for (Tag tag : note.getTagsList()) {
			tagList.add(tag);
		}
		
		for (Tag tag : tagList) {
			tag.removeNote(note);
		}
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_DELETE_NOTE, null, note);
	}
	
	public Tag addTag(String tagName) {
		Tag tag = new Tag(tagName);
		mTagsMap.put(tag.getId(), tag);		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_NEW_TAG, null, tag);
		return tag;
	}
	
	public void removeTag(Tag tag) {
		mTagsMap.remove(tag.getId());
		
		// First copy to a temporary list, as modifying a list we're looping on does not work. 
		List<BaseNote> baseNoteList = new ArrayList<BaseNote>();
		for (BaseNote task : tag.getTasks()) {
			baseNoteList.add(task);
		}
		for (BaseNote note : tag.getNotes()) {
			baseNoteList.add(note);
		}
		
		for (BaseNote task : baseNoteList) {
			removeTagFromBaseNote(task, tag);
		}
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_DELETE_TAG, null, null);
	}
	
	public void addTagToBaseNote(BaseNote baseNote, Tag tag) {
		baseNote.addTag(tag);
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_BASENOTE_TAG_CHANGE, null, baseNote);
	}
	
	public void removeTagFromBaseNote(BaseNote baseNote, Tag tag) {
		baseNote.removeTag(tag);
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_BASENOTE_TAG_CHANGE, null, baseNote);
	}
	
	public void addAttachementToBaseNote(BaseNote baseNote, AttachmentType type, String title, String link) {
		Attachment attachment = new Attachment(type, title, link);
		baseNote.getAttachmentsMap().put(attachment.getId(), attachment);
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_ADD_ATTACHMENT, null, baseNote);
	}
	
	public void modifyAttachmentFromBaseNote(BaseNote baseNote, Attachment attachment, AttachmentType type, String title, String link) {
		attachment.setType(type);
		attachment.setTitle(title);
		attachment.setLink(link);
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_ATTACHMENT, null, baseNote);
	}
	
	public void removeAttachmentFromBaseNote(BaseNote baseNote, Attachment attachement) {
		baseNote.getAttachmentsMap().remove(attachement.getId());
		
		mIsModified = true;
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_REMOVE_ATTACHMENT, null, baseNote);
	}
	
	private List<Task> getOpenTasksList() {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : mTasksMap.values()) {
			if (task.getStatus() == Task.TaskStatus.EnOpen) {
				result.add(task);
			}
		}
		
		return result;
	}
	
	public Map<UUID, Task> getTasksMap() {
		return mTasksMap;
	}
	
	public List<Task> getTasksList() {
		return getTasksList(true);
	}
	
	public List<Task> getTasksList(boolean includeClosedTask) {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : includeClosedTask ? mTasksMap.values() : getOpenTasksList()) {
			result.add(task);
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getStartingTodayTasksList() {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : getOpenTasksList()) {
			if (task.isStartingToday()) {
				result.add(task);
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getDueForTodayTasksList() {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : getOpenTasksList()) {
			if (task.isDueForToday()) {
				result.add(task);
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getUrgentTasksList() {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : getOpenTasksList()) {
			if ((task.isUrgent()) &&
					(!task.isLate())) {
				result.add(task);
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getLateTasksList() {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : getOpenTasksList()) {
			if (task.isLate()) {
				result.add(task);
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getUntaggedTasksList(boolean includeClosedTask) {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : includeClosedTask ? mTasksMap.values() : getOpenTasksList()) {
			if (task.getTagsList().size() == 0) {
				result.add(task);
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public List<Task> getFilteredTasksListByTag(List<Tag> tagList, boolean includeClosedTask) {
		List<Task> result = new ArrayList<Task>();
		
		for (Task task : includeClosedTask ? mTasksMap.values() : getOpenTasksList()) {
			
			for (Tag tag : tagList) {
				if (task.getTagsList().contains(tag)) {
					result.add(task);
					break;
				}
			}
		}
		
		Collections.sort(result, new TaskComparator());
		
		return result;
	}
	
	public Task getTaskById(UUID id) {								
		return mTasksMap.get(id);
	}
	
	public void openTaskById(UUID id) {
		Task task = mTasksMap.get(id);
		
		if (task != null) {
			new OpenTaskAction(task).run();
		} else {
			ApplicationUtils.notifyUser(Messages.Project_InvalidTaskTitle, String.format(Messages.Project_InvalidTaskMessage, id));
		}
	}
	
	public List<Note> getNotesList() {
		List<Note> result = new ArrayList<Note>();
		
		for (Note note : mNotesMap.values()) {
			result.add(note);
		}
		
		Collections.sort(result, new NoteComparator());
		
		return result;
	}
	
	public List<Note> getUntaggedNotesList() {
		List<Note> result = new ArrayList<Note>();
		
		for (Note note : mNotesMap.values()) {
			if (note.getTagsList().size() == 0) {
				result.add(note);
			}
		}
		
		Collections.sort(result, new NoteComparator());
		
		return result;
	}
	
	public List<Note> getFilteredNotesListByTag(List<Tag> tagList) {
		List<Note> result = new ArrayList<Note>();
		
		for (Note note : mNotesMap.values()) {
			
			for (Tag tag : tagList) {
				if (note.getTagsList().contains(tag)) {
					result.add(note);
					break;
				}
			}
		}
		
		Collections.sort(result, new NoteComparator());
		
		return result;
	}
	
	public Note getNoteById(UUID id) {								
		return mNotesMap.get(id);
	}
	
	public void openNoteById(UUID id) {
		Note note = mNotesMap.get(id);
		
		if (note != null) {
			new OpenNoteAction(note).run();
		} else {
			ApplicationUtils.notifyUser(Messages.Project_InvalidNoteTitle, String.format(Messages.Project_InvalidNoteMessage, id));
		}
	}
	
	public Map<UUID, Tag> getTagsMap() {
		return mTagsMap;
	}
	
	public List<Tag> getTagsList(SortDirection sortDirection) {
		List<Tag> result = new ArrayList<Tag>();
		
		for (Tag tag : mTagsMap.values()) {
			result.add(tag);
		}
		
		Collections.sort(result, new TagComparator(sortDirection));
	
		return result;
	}
	
	public Tag getTagById(UUID id) {
		return mTagsMap.get(id);
	}
	
	public Tag getTagByName(String name) {
		Tag result = null;
		
		for (Tag tag : mTagsMap.values()) {
			if (tag.getName().equals(name)) {
				result = tag;
				break;
			}
		}
		
		return result;
	}
	
	public boolean isModified() {
		return mIsModified;
	}
	
	public void setModified(boolean value) {
		mIsModified = value;
	}
	
	public void renameBaseNote(BaseNote baseNote, String newTitle) {
		baseNote.setTitle(newTitle);
		mIsModified = true;
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_RENAME_BASENOTE, null, baseNote);
	}
	
	public void setBaseNoteContent(BaseNote note, String content) {
		note.setContent(content);
		mIsModified = true;
	}
	
	public void setTaskPriority(Task task, int priority) {
		task.setPriority(priority);
		mIsModified = true;
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_TASK, null, task);
	}
	
	public void setTaskStartDate(Task task, Date startDate) {
		task.setStartDate(startDate);
		mIsModified = true;
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_TASK, null, task);
	}
	
	public void setTaskDueDate(Task task, Date dueDate) {
		task.setDueDate(dueDate);
		mIsModified = true;
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_TASK, null, task);
	}
	
	public void setTaskStatus(Task task, TaskStatus status) {
		task.setStatus(status);
		mIsModified = true;
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_TASK, null, task);
	}
	
}
